Le tour du monde en quatre-vingt jours
Le tour du monde en quatre-vingt jours
Introduction
- Ceci est un document présentant un test de lémmatisation avec R à partir du lexique Morphalou 3.1 de Ortolang.
- Le texte utilisé est “Le tour du monde en quatre-vingt jours” de Jules Verne, récupéré en ligne sur le projet Gutenberg.
library(tidyverse)
library(tidytext)
# Le tour du monde en quatre-vingts jours
# book <- gutenberg_download(3456)
# récupérer le livre en latin1
book <- tibble(text = readr::read_lines('http://www.gutenberg.org/files/3456/3456-8.txt',
locale = readr::locale(encoding = "latin1")))
# du début à la FIN du livre
# book[632:9888,] %>%
# mutate(text = stringi::stri_trans_general(text,"Latin-ASCII"))-> book_
book[632:9888,] -> book_Découpage mot par mot et lemmatisation
Avec le package tidytext, on découpe mot par mot.
On applique une regexpr pour enlever les _ autour de _mot_ (ce qui correspond à l’italique dans les livres sur le projet gutenberg).
par_phrases <- book_ %>%
summarise(texte = paste0(text, collapse = " ")) %>%
mutate(texte = stringr::str_replace_all(texte, '\'', ' ')) %>%
unnest_tokens(phrases, texte, token = "sentences") %>%
mutate(phrase_number = row_number())
par_phrase_mot <- par_phrases %>%
unnest_tokens(word, phrases, token = "words") %>%
#mutate(word = stringr::str_extract(word, '[a-z0-9]+'))
mutate(word = stringr::str_replace_all(word, '_', ''))Quelques exemples de mots
book_ %>%
unnest_tokens(mot, text, token = "ngrams", n = 1) -> mots
glimpse(mots, width = 600)Observations: 68,222
Variables: 1
$ mot <chr> "dans", "lequel", "phileas", "fogg", "et", "passepartout", "s'acceptent", "réciproquement", "l'un", "comme", "maître", "l'autre", "comme", "domestique", "en", "l'année", "1872", "la", "maison", "portant", "le", "numéro", "7", "de", "saville", "row", "burlington", "gardens", "maison", "dans", "laquelle", "sheridan", "mourut", "en", "1814", "était", "habitée", "par", "phileas", "fogg", "esq", "l'un", "des", "membres", "les", "plus", "singuliers", "et", "les", "plus", "remarqués", "du", "reform", "club", "de", "londres", "bien", "qu'il", "semblât", "prendre", "à", "tâche", "de", ...
Aperçu de la table lexique de Morphalou 3.1
On donne un aperçu sur 30 lignes tirées au sort dans le fichier, en sélectionnant certaines variables ici.
# lecture du fichier produit à partir du csv Morphalou 3.1
test <- readr::read_csv('results/tidy_morphalou3.1.csv.gz')
dtttable(
sample_n(test %>%
select(forme_lemmatise, dictionnaire,
forme_flechie, GENRE_1, NOMBRE,
TEMPS, PERSONNE),
30), n = 15
)Processus 1
On cherche à rapprocher la forme lémmatisée correspondant la plus proche du mot pour produire un étiquetage grammatical.
# Si le mot présente des formes fléchies, forme fléchie, sinon on place l'unique forme
# lemmatisée dans la forme fléchie
test <- test %>%
mutate(forme_flechie = case_when(
is.na(forme_flechie) ~ forme_lemmatise,
!is.na(forme_flechie) ~ forme_flechie
))
# nombre de caractères de la forme lemmatisée, on enlève les accents que l'on laissera sur la forme lemmatisée
test2 <- test %>%
mutate(nc = nchar(forme_lemmatise)) # %>%
# mutate(forme_flechie = stringi::stri_trans_general(forme_flechie,"Latin-ASCII"))
# Distance entre le mot orginal et la forme lemmatise
par_phrase_mot %>%
mutate(nc0 = nchar(word),
rn = row_number()) %>%
left_join(test2,
by = c('word' = 'forme_flechie')) %>%
mutate(diff = stringdist::stringsim(word, forme_lemmatise)) %>%
arrange(rn, word, desc(diff)) %>%
distinct(rn, .keep_all = T) %>%
mutate(word_2 = stringr::str_extract(word, '[a-z0-9]+')) %>%
filter(! stringr::str_detect('[a-z]{1}', word_2)) %>%
filter(! stringr::str_detect('[0-9]+', word_2)) %>%
filter(! word_2 %in% tm::stopwords('french')) -> words_lemmesNombre de mots / nombre de lemmes
words_lemmes %>%
summarise(nmots = n_distinct(word),
nlemmes = n_distinct(forme_lemmatise)) %>%
knitr::kable()| nmots | nlemmes |
|---|---|
| 8316 | 5480 |
Aperçu des lemmes trouvés
On donne un aperçu sur quelques lignes tirées au sort dans le livre.
dtttable(sample_n(words_lemmes %>%
filter(!is.na(forme_lemmatise)) %>%
select(word, forme_lemmatise, dictionnaire,
GENRE_1, NOMBRE,
TEMPS, PERSONNE), 600), n = 15)Aperçu des mots non lemmatisés
On donne un aperçu de mots non trouvés.
dtttable(count(words_lemmes %>%
filter(is.na(forme_lemmatise)),
word, sort = T), n = 20)Résultat de l’étiquetage grammatical
Dans un premier temps observons le résultat de l’étiquetage grammatical
Catégories
words_lemmes %>%
filter(!is.na(forme_lemmatise)) %>%
count(`CATÉGORIE`, sort = T) %>%
mutate(p = n / sum(n)) %>%
head(20) %>%
knitr::kable()| CATÉGORIE | n | p |
|---|---|---|
| Nom commun | 18801 | 0.5438531 |
| Verbe | 7186 | 0.2078681 |
| Adjectif qualificatif | 2929 | 0.0847266 |
| Adverbe | 2744 | 0.0793752 |
| Nombre | 901 | 0.0260631 |
| Conjonction | 677 | 0.0195835 |
| Préposition | 538 | 0.0155626 |
| Pronom | 473 | 0.0136824 |
| Déterminant | 214 | 0.0061903 |
| Interjection | 107 | 0.0030952 |
Verbes
Formes infinitives
words_lemmes %>%
filter(grepl('^verbe', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.002, rot.per = 0)})Mode, temps, personne
words_lemmes %>%
filter(grepl('^verbe', dictionnaire)) %>%
count(MODE, TEMPS, PERSONNE, sort = T) %>%
head(20) %>%
knitr::kable()| MODE | TEMPS | PERSONNE | n |
|---|---|---|---|
| indicative | imperfect | thirdPerson | 2332 |
| indicative | simplePast | thirdPerson | 1619 |
| infinitive | - | - | 1491 |
| indicative | present | thirdPerson | 432 |
| indicative | present | secondPerson | 240 |
| participle | past | - | 228 |
| indicative | present | firstPerson | 222 |
| subjunctive | imperfect | thirdPerson | 172 |
| participle | present | - | 144 |
| conditional | present | thirdPerson | 137 |
| indicative | future | thirdPerson | 48 |
| indicative | future | firstPerson | 40 |
| indicative | imperfect | secondPerson | 24 |
| indicative | future | secondPerson | 17 |
| conditional | present | secondPerson | 11 |
| subjunctive | present | thirdPerson | 7 |
| imperative | present | secondPerson | 5 |
| indicative | imperfect | firstPerson | 5 |
| subjunctive | present | firstPerson | 5 |
| indicative | simplePast | secondPerson | 4 |
Adverbes
words_lemmes %>%
filter(grepl('^adverbe', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.002, rot.per = 0)})Prépositions
words_lemmes %>%
filter(grepl('^préposition', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.0015, rot.per = 0)})Noms communs
words_lemmes %>%
filter(grepl('^nom commun', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.002, rot.per = 0)})Adjectifs qualificatifs
words_lemmes %>%
filter(grepl('^adjectif qualific', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.002, rot.per = 0)})N.B.: Passepartout est en fait l’un des personnages principaux du roman.
Pronoms
words_lemmes %>%
filter(grepl('^pronom', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.0015, rot.per = 0)})Déterminants
words_lemmes %>%
filter(grepl('^déterminant', dictionnaire)) %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.0015, rot.per = 0)})Nombres
words_lemmes %>%
filter(!is.na(forme_lemmatise)) %>%
count(NOMBRE, sort = T) %>%
mutate(p = n / sum(n)) %>%
head(20) %>%
knitr::kable()| NOMBRE | n | p |
|---|---|---|
| singular | 20529 | 0.5938386 |
| - | 5817 | 0.1682673 |
| plural | 5811 | 0.1680937 |
| invariable | 2413 | 0.0698004 |
Genres
words_lemmes %>%
filter(!is.na(GENRE_1) & GENRE_1 != '-') %>%
count(GENRE_1, sort = T) %>%
mutate(p = n / sum(n)) %>%
head(20) %>%
knitr::kable()| GENRE_1 | n | p |
|---|---|---|
| invariable | 2156 | 0.4574581 |
| masculine | 2023 | 0.4292383 |
| feminine | 534 | 0.1133036 |
Processus 2
On cherche à rapprocher la forme lémmatisée sans produire d’étiquetage grammatical. On se contente de réduire le nombre de mots possibles (pluriel mis au singulier, féminin au masculin, formes conjuguées à l’infinitif, etc.)
# Si le mot présente des formes fléchies, forme fléchie, sinon on place l'unique forme
# lemmatisée dans la forme fléchie
test <- test %>%
mutate(forme_flechie = case_when(
is.na(forme_flechie) ~ forme_lemmatise,
!is.na(forme_flechie) ~ forme_flechie
))
test2 <- test %>%
mutate(nc = nchar(forme_lemmatise)) %>%
distinct(forme_flechie, forme_lemmatise, NOMBRE, GENRE, dictionnaire)
# Distance entre le mot orginal et la forme lemmatise
par_phrase_mot %>%
mutate(nc0 = nchar(word),
rn = row_number()) %>%
left_join(test2,
by = c('word' = 'forme_flechie')) %>%
mutate(diff = stringdist::stringsim(word, forme_lemmatise)) %>%
arrange(rn, word, desc(NOMBRE), desc(diff)) %>% # desc(GENRE),
distinct(rn, .keep_all = T) %>%
mutate(word_2 = stringr::str_extract(word, '[a-z0-9]+')) %>%
filter(! stringr::str_detect('[a-z]{1}', word_2)) %>%
filter(! stringr::str_detect('[0-9]+', word_2)) %>%
filter(! word_2 %in% tm::stopwords('french')) -> words_lemmesNombre de mots / nombre de lemmes
words_lemmes %>%
summarise(nmots = n_distinct(word),
nlemmes = n_distinct(forme_lemmatise)) %>%
knitr::kable()| nmots | nlemmes |
|---|---|
| 8316 | 5504 |
Nuages de lemmes
words_lemmes %>%
count(forme_lemmatise, sort = T) %>%
mutate(p = n / sum(n)) %>%
(function(df){
wordcloud::wordcloud(words = df$forme_lemmatise, freq = df$p, min.freq = 0.0012, rot.per = 0)})Les formes lémmatisées
On compte les associations entre formes fléchies (mots du corpus) et formes lémmatisées (mots du lexique) lorsque les deux sont renseignés (lemme existant) et que l’une diffère de l’autre.
Table
count(words_lemmes %>%
filter(!is.na(forme_lemmatise),
word != forme_lemmatise),
word, forme_lemmatise, sort = TRUE) %>%
dtttable()Graphes
n > 20 occurences
library(igraph)
library(ggraph)
count(words_lemmes %>%
filter(!is.na(forme_lemmatise),
word != forme_lemmatise),
word, forme_lemmatise, sort = TRUE) %>%
filter(n > 20) %>%
graph_from_data_frame() -> g
a <- grid::arrow(type = "closed", length = unit(.05, "inches"))
ggraph(g, layout = "fr") +
geom_edge_link(aes(edge_alpha = n), show.legend = T,
arrow = a, end_cap = circle(.07, 'inches')) +
geom_node_point(color = "lightblue", size = 3) +
geom_node_text(aes(label = name), vjust = 1, hjust = 1, size = 2) +
theme_void() +
ggtitle("Formes fléchies - formes lémmatisées") -> p
p11 < n < 21 occurences
count(words_lemmes %>%
filter(!is.na(forme_lemmatise),
word != forme_lemmatise),
word, forme_lemmatise, sort = TRUE) %>%
filter(n < 21, n > 11) %>%
graph_from_data_frame() -> g
a <- grid::arrow(type = "closed", length = unit(.05, "inches"))
ggraph(g, layout = "fr") +
geom_edge_link(aes(edge_alpha = n), show.legend = T,
arrow = a, end_cap = circle(.07, 'inches')) +
geom_node_point(color = "lightblue", size = 3) +
geom_node_text(aes(label = name), vjust = 1, hjust = 1, size = 2) +
theme_void() +
ggtitle("Formes fléchies - formes lémmatisées") -> p
p6 < n < 12 occurences
library(igraph)
library(ggraph)
count(words_lemmes %>%
filter(!is.na(forme_lemmatise),
word != forme_lemmatise),
word, forme_lemmatise, sort = TRUE) %>%
filter(n < 12, n > 6) %>%
graph_from_data_frame() -> g
a <- grid::arrow(type = "closed", length = unit(.03, "inches"))
ggraph(g, layout = "fr") +
geom_edge_link(aes(edge_alpha = n), show.legend = T,
arrow = a, end_cap = circle(.03, 'inches')) +
geom_node_point(color = "lightblue", size = 1) +
geom_node_text(aes(label = name), vjust = 1, hjust = 1, size = 2) +
theme_void() +
ggtitle("Formes fléchies - formes lémmatisées") -> p
pDiscussion
Sur le processus 1
- Les mots non lémmatisés correspondent le plus souvent à des noms de personnages, des noms de lieux.
- La méthode développée (rapprocher les formes fléchies des mots du corpus, sélectionner la forme lémmatisée minimisant la distance “stringdist” entre celui-ci et le mot d’origine) ne tient pas compte de la structure des phrases.
- On observe donc dans les nuages de mots que l’étiquatage grammatical n’est pas satisfaisant : des verbes sont aussi des noms communs (avoir, dire, être). On peut aussi observer que certains adjectifs sont des verbes au participe présent (compris, trouvé), de même pour “or” et “car”. Il faudrait tenir compte de la structure morpho-syntaxique pour attribuer correctement le bon étiquetage grammatical à chaque mot, et ainsi tenir compte de l’ordonnocement des mots dans la phrase.
- Ce problème ne se pose pas lorsqu’un mot ne correspond qu’à une seule étiquette grammaticale (la plupart des adverbes, pronoms et déterminants). Et l’approche proposée permet de réduire le nombre de formes dans un document. Étiqueter toutes les formes conjuguées d’un verbe en son infinitif permet déjà de réduire une part importante du nombre de formes.
- Et étudier les structures morpho-syntaxique est d’une difficulté plus enlevée (ce que font de bons outils de lemmatisation !).
Sur le processus 2
- Beaucoup plus simple, on s’attache à rattacher les singuliers et masulin / invariant aux mots.
- On ne gagne en revanche aucune information grammaticale sur les mots.
- Il y a des erreurs, qu’il faudrait quantifier à partir de la table de la sous-partie “formes lémmatisées”.
Simap - DOMU